import java.io.*;
import java.util.*;

public class DataManager
{
	// The instance
	private static DataManager dm = null;
	private static boolean initializing = false;

	// Note all casing is lower-case except here
	// Maps IRC nicks and Summoners to their proper casing
	HashMap<String, String> pNick = new HashMap<String, String>();
	HashMap<String, String> pSumm = new HashMap<String, String>();

	// Maps IRC nicks to Summoners and vice-versa
	HashMap<String, String> nickToSumm = new HashMap<String, String>();
	HashMap<String, String> summToNick = new HashMap<String, String>();

	// Player data
	HashMap<String, Player> playerData = new HashMap<String, Player>();

	// List of active players (IRC nicks) mapped to the game they are in
	HashMap<String, Game> gameMap = new HashMap<String, Game>();

	// The current match pools
	HashMap<Integer, Match> pools = new HashMap<Integer, Match>();

	// List of games (used to find contested games)
	ArrayList<Game> games = new ArrayList<Game>();

	// History of all matches
	HashMap<Integer, String> matchHistory = new HashMap<Integer, String>();

	// List of reports
	ArrayList<Report> reports = new ArrayList<Report>();

	private DataManager()
	{
		initializing = true;
		load();
		initializing = false;
	}

	public static DataManager getInstance()
	{
		if (!initializing && dm == null)
			dm = new DataManager();

		return dm;
	}

	/**
	 * Loads persistent state information
	 * Sets up maps
	 */
	public void load()
	{
		// Create files if necessary (technically the exists check isn't needed)
		File f;

		try
		{
			f = new File("playerdata.txt");
			if (!f.exists())
				f.createNewFile();

			f = new File("match_history.txt");
			if (!f.exists())
				f.createNewFile();

			f = new File("lobby_data.txt");
			if (!f.exists())
				f.createNewFile();

			f = new File("game_data.txt");
			if (!f.exists())
				f.createNewFile();

			f = new File("reports.txt");
			if (!f.exists())
				f.createNewFile();
		}
		catch (IOException e)
		{
			System.out.println("Failed to create initial files");
			e.printStackTrace();
			System.exit(0);
		}

		try
		{
			BufferedReader in;
			String line;

			// Read in player data
			in = new BufferedReader(new FileReader("playerdata.txt"));
			while ((line = in.readLine()) != null)
			{
				if (line.length() == 0)
					continue;

				String[] data = line.split(" ");

				Player p = new Player();
				p.summoner = data[1].toLowerCase();
				p.rating = Integer.parseInt(data[2]);
				p.games = Integer.parseInt(data[3]);
				p.wins = Integer.parseInt(data[4]);
				p.warn = Integer.parseInt(data[5]);
				p.registered = (data[6].charAt(0) == '1');
				p.moderator = (data[7].charAt(0) == '1');
				p.admin = (data[8].charAt(0) == '1');

				pNick.put(data[0].toLowerCase(), data[0]);
				pSumm.put(data[1].toLowerCase(), data[1]);

				nickToSumm.put(data[0].toLowerCase(), data[1].toLowerCase());
				summToNick.put(data[1].toLowerCase(), data[0].toLowerCase());

				playerData.put(data[0].toLowerCase(), p);
			}
			in.close();

			// Read in match history
			in = new BufferedReader(new FileReader("match_history.txt"));
			while ((line = in.readLine()) != null)
			{
				if (line.length() == 0)
					continue;

				int idx = line.indexOf(" ");
				int id = 0;
				try { id = Integer.parseInt(line.substring(0, idx)); } catch (Exception e) { }
				idx = line.indexOf(" ", idx);
				matchHistory.put(id, line.substring(idx).trim());
			}
			in.close();

			// Restore a previous lobby
			in = new BufferedReader(new FileReader("lobby_data.txt"));
			while ((line = in.readLine()) != null)
			{
				if (line.length() == 0)
					continue;

				String[] parts = line.split(" ");

				Match temp = new Match();

				temp.state = Integer.parseInt(parts[0]);
				String[] players = parts[1].split(",");
				for (String s : players)
					temp.playerlist.add(s);

				temp.type = Integer.parseInt(parts[2]);
				temp.mods = Integer.parseInt(parts[3]);

				pools.put(temp.type, temp);

				// Make an AFK check if necessary
				addPlayer(null, temp.type);
			}
			in.close();

			// Restore previous games
			in = new BufferedReader(new FileReader("game_data.txt"));
			while ((line = in.readLine()) != null)
			{
				if (line.length() == 0)
					continue;

				String[] parts = line.split(" ");
				Game g = new Game(Integer.parseInt(parts[1]));
				g.id = Integer.parseInt(parts[0]);
				g.start_time = Long.parseLong(parts[2]);
				String[] players = parts[3].split(",");
				for (String s : players)
				{
					g.team1.add(s);
					g.players.add(s);
					gameMap.put(s, g);
				}
				players = parts[4].split(",");
				for (String s : players)
				{
					g.team2.add(s);
					g.players.add(s);
					gameMap.put(s, g);
				}

				games.add(g);
				matchHistory.put(g.id, "");
			}
			in.close();

			// Read reports
			in = new BufferedReader(new FileReader("reports.txt"));
			while ((line = in.readLine()) != null)
			{
				// String.split is weird and will consider adjacent delimiters to be a single delimiter
				line = line.replaceAll("\\|", " |") + " "; // So add a space before each (and at the end)

				String[] parts = line.split("\\|");
				Report r = new Report();

				r.reporter = parts[0].trim();
				r.player = parts[1].trim();
				r.message = parts[2].trim();
				r.resolved = (parts[3].charAt(0) == '1' ? true : false);
				r.admin = parts[4].trim();
				r.notes = parts[5];

				reports.add(r);
			}
			in.close();
		}
		catch (Exception e) { e.printStackTrace(); }
	}

	/**
	 * Saves the player data
	 */
	public void dumpPlayers()
	{
		try
		{
			BufferedWriter out = new BufferedWriter(new FileWriter("playerdata.txt"));
			for (String name : playerData.keySet())
			{
				Player p = playerData.get(name);
				out.write(
					String.format("%s %s %d %d %d %d %d %d %d\n",
								  pNick.get(name),
								  pSumm.get(p.summoner),
								  p.rating,
								  p.games,
								  p.wins,
								  p.warn,
								  (p.registered ? 1 : 0),
								  (p.moderator ? 1 : 0),
								  (p.admin ? 1 : 0)));
			}
			out.close();
		}
		catch (Exception e) { e.printStackTrace(); }
	}

	/**
	 * Saves the match data
	 */
	public void dumpHistory()
	{
		try
		{
			BufferedWriter out = new BufferedWriter(new FileWriter("match_history.txt"));
			for (int i = 0; i < matchHistory.size(); i++)
			{
				String hist = matchHistory.get(i);
				if (hist != null && hist.length() > 0)
					out.write(i + " " + hist + "\n");
			}
			out.close();
		}
		catch (Exception e) { e.printStackTrace(); }
	}

	/**
	 * Saves the current game data
	 */
	public void dumpGames()
	{
		try
		{
			BufferedWriter out = new BufferedWriter(new FileWriter("game_data.txt"));
			for (Game g : games)
			{
				out.write(g.id + " ");
				out.write(g.team_size + " ");
				out.write(g.start_time + " ");
				for (String s : g.team1)
					out.write(s + ",");
				out.write(" ");
				for (String s : g.team2)
					out.write(s + ",");
				out.write("\n");
			}
			out.close();
		}
		catch (Exception e) { e.printStackTrace(); }
	}

	/**
	 * Saves the current lobby
	 */
	public void dumpLobby()
	{
		try
		{
			BufferedWriter out = new BufferedWriter(new FileWriter("lobby_data.txt"));

			// If there's no game, we just want to clear the file
			if (pools.size() != 0)
			{
				for (Match m : pools.values())
				{
					out.write(m.state + " ");
					for (String s : m.playerlist)
						out.write(s + ",");
					out.write(" ");
					out.write(m.type + " ");
					out.write(m.mods + " ");
				}
			}

			out.close();
		}
		catch (Exception e) { e.printStackTrace(); }
	}

	/**
	 * Saves report data
	 */
	public void dumpReports()
	{
		try
		{
			BufferedWriter out = new BufferedWriter(new FileWriter("reports.txt"));
			for (Report r : reports)
			{
				out.write(r.reporter + "|");
				out.write(r.player + "|");
				out.write(r.message + "|");
				out.write((r.resolved ? "1" : "0") + "|");
				out.write(r.admin + "|");
				out.write(r.notes + "\n");
			}
			out.close();
		}
		catch (Exception e) { e.printStackTrace(); }
	}

	/**
	 * Returns true if the IRC nick is registered
	 */
	public boolean isRegistered(String nick)
	{
		if (nick == null)
			return false;

		nick = nick.toLowerCase();
		return (playerData.containsKey(nick) && playerData.get(nick).registered);
	}

	/**
	 * Returns true if the IRC nick is a moderator
	 */
	public boolean isMod(String nick)
	{
		if (nick == null)
			return false;

		nick = nick.toLowerCase();
		return (playerData.containsKey(nick) && playerData.get(nick).moderator);
	}

	/**
	 * Returns true if the IRC nick is an admin
	 */
	public boolean isAdmin(String nick)
	{
		if (nick == null)
			return false;

		nick = nick.toLowerCase();
		return (playerData.containsKey(nick) && playerData.get(nick).admin);
	}

	/**
	 * Returns true if the specified nick is in a game
	 */
	public boolean isInGame(String nick)
	{
		return gameMap.containsKey(nick.toLowerCase());
	}

	/**
	 * Returns true if the player is in a pool
	 */
	public boolean isInPool(String nick)
	{
		boolean inPool = false;
		for (Match m : pools.values())
		{
			if (m.playerlist.contains(nick.toLowerCase()))
				inPool = true;
		}
		return inPool;
	}

	/**
	 * Returns the player data that belongs to the specified IRC nick
	 */
	public Player getPlayer(String nick)
	{
		// Assumes the nick is valid
		return playerData.get(nick.toLowerCase());
	}

	/**
	 * Returns the summoner for the specified IRC nick
	 */
	public String getSumm(String nick)
	{
		// Assumes the nick is valid
		return pSumm.get(nickToSumm.get(nick.toLowerCase()));
	}

	/**
	 * Returns the IRC nick for the specified summoner
	 */
	public String getNick(String summ)
	{
		// Assumes the nick is valid
		return pNick.get(summToNick.get(summ.toLowerCase()));
	}

	/**
	 * Clears and saves pools (called from a timed out AFK check)
	 */
	public void clearPools()
	{
		for (int type : pools.keySet())
		{
			Match m = pools.get(type);
			if (m.playerlist.size() == 0)
				pools.remove(type);
		}

		dumpLobby();
	}

	/**
	 * Returns a list of players and their summoner names in the current pools
	 */
	public String listPools()
	{
		StringBuffer buffer = new StringBuffer();
		for (Match m : pools.values())
		{
			buffer.append("In the pool for ");
			buffer.append(m.getType());
			buffer.append(": ");
			buffer.append(listPlayers(m.playerlist));
			buffer.append("\n");
		}
		if (buffer.length() > 0)
			return buffer.toString();
		else
			return "No pools are running at this time";
	}
	private String listPlayers(HashSet<String> nicks)
	{
		String ret = "";
		for (String player : nicks)
		{
			//ret += pNick.get(player) + " (" + pSumm.get(nickToSumm.get(player)) + ") ";
			ret += getSumm(player) + " ";
		}
		return ret;
	}
	private String listPlayers(ArrayList<String> nicks)
	{
		String ret = "";
		for (String player : nicks)
		{
			//ret += pNick.get(player) + " (" + pSumm.get(nickToSumm.get(player)) + ") ";
			ret += getSumm(player) + " ";
		}
		return ret;
	}
	private String listNicks(ArrayList<String> nicks)
	{
		String ret = "";
		for (String player : nicks)
		{
			ret += pNick.get(player) + " ";
		}
		return ret;
	}

	/**
	 * Returns statistics for the given nick
	 */
	public String getStats(String nick)
	{
		return getStats(nick, false);
	}

	/**
	 * Returns statistics for the given nick.  Warn is included if the second parameter is true
	 */
	public String getStats(String nick, boolean all)
	{
		Player p = getPlayer(nick);
		String stats = pSumm.get(p.summoner) + " " + p.wins + "W / " + (p.games - p.wins) + "L (" + p.rating + ")";
		if (all)
			stats += ", " + p.warn + " warn";

		return stats;
	}

	/**
	 * Returns the names, ratings, and W/L of the top N summoners
	 */
	public String getTop(int n)
	{
		if (n > 10)
			n = 10;

		ArrayList<Player> players = new ArrayList<Player>();
		players.addAll(playerData.values());
		Collections.sort(players, new Comparator<Player>() {
			public int compare(Player o1, Player o2)
			{
				// % games won is the tiebreaker
				return (o2.rating - o1.rating) * 1000 + (1000 * o2.wins / (o2.games+1) - 1000 * o1.wins / (o1.games+1));
			}
		});

		// Don't crash if there's few summoners
		if (n > players.size())
			n = players.size();

		StringBuffer buff = new StringBuffer();
		for (int i = 0; i < n; i++)
		{
			Player p = players.get(i);
			buff.append(pSumm.get(p.summoner) + " " + p.wins + "W / " + (p.games - p.wins) + "L (" + p.rating + ")");
			if (i < n - 1)
				buff.append("; ");
		}

		return buff.toString();
	}

	/**
	 * Returns the outcome of the given match ID
	 */
	public String getHistory(int id)
	{
		if (!matchHistory.containsKey(id) || matchHistory.get(id).length() == 0)
			return null;

		return matchHistory.get(id);
	}

	/**
	 * Returns the last 5 game ID's for the given nick
	 */
	public String getHistory(String nick)
	{
		String summ = getSumm(nick).toLowerCase();
		String idlist = "";
		int id = matchHistory.size() - 1;
		int max = Math.min(5, getPlayer(nick).games);
		for (int i = 0; i < max; i++)
		{
			while (id >= 0 && (!matchHistory.get(id).toLowerCase().contains(summ) || matchHistory.get(id).contains("Voided")))
				id--;

			if (id < 0)
				break;

			idlist += id + " ";
			id--;
		}

		return idlist;
	}

	/**
	 * Registers a player in the league
	 */
	public void registerPlayer(String nick, String summ)
	{
		// Note: Changing casing will hit the first condition
		// (even though it qualifies for the second as well)

		// Handle changing nick
		if (pSumm.containsKey(summ.toLowerCase()))
		{
			for (String oldnick : playerData.keySet())
			{
				Player p = playerData.get(oldnick);
				if (p.summoner.equals(summ.toLowerCase()))
				{
					pNick.remove(summToNick.get(p.summoner));
					nickToSumm.remove(summToNick.get(p.summoner));

					playerData.remove(oldnick);
					playerData.put(nick.toLowerCase(), p);
					break;
				}
			}
		}
		// Handle changing summoner
		else if (playerData.containsKey(nick.toLowerCase()))
		{
			pSumm.remove(nickToSumm.get(nick.toLowerCase()));
			summToNick.remove(nickToSumm.get(nick.toLowerCase()));
		}
		// Handle new registration
		else
		{
			playerData.put(nick.toLowerCase(), new Player());
		}

		pNick.put(nick.toLowerCase(), nick);
		pSumm.put(summ.toLowerCase(), summ);
		nickToSumm.put(nick.toLowerCase(), summ.toLowerCase());
		summToNick.put(summ.toLowerCase(), nick.toLowerCase());

		getPlayer(nick).registered = true;
		getPlayer(nick).summoner = summ.toLowerCase();
	}

	/**
	 * Give a player the specified amount of warn
	 */
	public void warn(String nick, int val)
	{
		Player p = getPlayer(nick);
		p.warn = Math.max(0, p.warn + val);

		dumpPlayers();
	}

	/**
	 * Adds a report
	 */
	public void addReport(String nick, String player, String message)
	{
		reports.add(new Report(nick, player, message));
		dumpReports();

		LeagueBot.sendMsg(nick, "Report added");
		LeagueBot.sendMsg(LeagueBot.achannel, "SYSTEM - " + getSumm(nick) + " reported " + player + " for '" + message + "'");
		LeagueBot.logAdmin("SYSTEM - " + getSumm(nick) + " reported " + player + " for '" + message + "'");
	}

	/**
	 * Returns a report
	 */
	public String getReport(int id)
	{
		if (id >= reports.size() || id < 0)
			return null;

		Report r = reports.get(id);
		String ret = r.reporter + " reported " + r.player + " for '" + r.message + "'";

		if (r.resolved)
			ret += "\nResolved by " + r.admin + " - " + r.notes;

		return ret;
	}

	/**
	 * Returns a list of unresolved reports
	 */
	public String listReports()
	{
		StringBuffer buff = new StringBuffer();

		for (int i = 0; i < reports.size(); i++)
		{
			Report r = reports.get(i);
			if (!r.resolved)
			{
				buff.append(",");
				buff.append(i);
			}
		}

		if (buff.length() == 0)
			return "No unresolved reports";
		else
			return "Unresolved report IDs: " + buff.toString().substring(1);
	}

	/**
	 * Resolves a report
	 */
	public boolean resolveReport(String nick, int id, String notes)
	{
		if (id >= reports.size() || id < 0)
			return false;

		Report r = reports.get(id);
		r.resolved = true;
		r.admin = nick;
		r.notes = notes;

		dumpReports();

		return true;
	}

	/**
	 * Adds a player to the default pool
	 */
	public void addPlayer(String nick)
	{
		addPlayer(nick, Match.FIVE_IH);
	}

	/**
	 * Adds a player to the specified pool
	 */
	public void addPlayer(String nick, int type)
	{
		// Create the pool if needed
		if (!pools.containsKey(type))
		{
			Match temp = new Match();
			temp.type = type;
			pools.put(type, temp);
		}

		Match match = pools.get(type);

		// Add the player if this isn't just a forced AFK check
		if (nick != null)
		{
			if (match.playerlist.size() == match.maxPlayers() - 1)
			{
				LeagueBot.sendMsg(LeagueBot.channel, getSumm(nick) + " has joined the pool for " + match.getType());

				match.state = Match.FULL_GAME;
				LeagueBot.sendMsg(LeagueBot.channel, "AFK Check for " + match.getType() + "! " + listNicks(match.playerlist));
				LeagueBot.sendMsg(LeagueBot.channel, "The next " + match.maxPlayers() + " players to join or pass the AFK check will be matched");
				match.startAFKCheck();

				// Last player is automatically not AFK
				match.notAFK.add(nick.toLowerCase());

				// Add after, so the last player doesn't have to AFK check
				match.playerlist.add(nick.toLowerCase());
			}
			else if (match.state == Match.FULL_GAME)
			{
				// Only allow them to join if 30s has passed
				if (match.canSnipe())
				{
					// Anyone that joins after the AFK check automatically is not AFK
					match.notAFK.add(nick.toLowerCase());
					match.playerlist.add(nick.toLowerCase());

					LeagueBot.sendMsg(LeagueBot.channel, getSumm(nick) + " has joined the pool for " + match.getType());
				}
			}
			else
			{
				match.playerlist.add(nick.toLowerCase());

				LeagueBot.sendMsg(LeagueBot.channel, getSumm(nick) + " has joined the pool for " + match.getType());
			}

			// Cleanup the nick if applicable
			pNick.put(nick.toLowerCase(), nick);

			// Start a game if the AFK check passed
			if (match.notAFK.size() == match.maxPlayers())
				makeGame(type);

			dm.dumpLobby();
		}
		// Forced AFK check
		else if (match.state == Match.FULL_GAME)
		{
			// These will be queued since the IRC connection isn't started yet
			LeagueBot.sendMsg(LeagueBot.channel, "AFK Check! " + listNicks(match.playerlist));
			LeagueBot.sendMsg(LeagueBot.channel, "The next " + match.maxPlayers() + " players to join or pass the AFK check will be matched");

			match.startAFKCheck();
		}
	}

	/**
	 * Marks a player as not AFK if necessary
	 */
	public void markNotAFK(String nick)
	{
		nick = nick.toLowerCase();
		for (int type : pools.keySet())
		{
			Match m = pools.get(type);
			if (m.playerlist.contains(nick) && m.state == Match.FULL_GAME)
			{
				m.notAFK.add(nick);

				// Start a game if the AFK check passed
				if (m.notAFK.size() == m.maxPlayers())
					makeGame(type);
			}
		}
	}

	/**
	 * Removes a player from the pools
	 */
	public int removePlayer(String nick)
	{
		nick = nick.toLowerCase();
		for (int type : pools.keySet())
		{
			Match m = pools.get(type);
			if (m.playerlist.contains(nick))
			{
				// Note: AFK check will be handled by timeout

				m.playerlist.remove(nick);
				m.notAFK.remove(nick);
				if (m.playerlist.size() == 0)
					pools.remove(type);
				return type;
			}
		}

		return -1;
	}

	/**
	 * Creates a game
	 */
	public void makeGame(int type)
	{
		Match match = pools.get(type);

		Game g = null;
		if (match.type == Match.FIVE_IH)
			g = new Game(5);
		else if (match.type == Match.THREE_IH)
			g = new Game(3);
		else if (match.type == Match.ONE_VS_ONE)
			g = new Game(1);

		// Shuffle the players
		ArrayList<String> playerlist = new ArrayList<String>();
		for (String nick : match.notAFK)
		{
			match.playerlist.remove(nick);
			playerlist.add(nick);
		}
		Collections.shuffle(playerlist);

		// Reset/Clear state vars
		match.endAFKCheck();
		if (match.playerlist.size() == 0)
		{
			pools.remove(type);
		}
		else if (match.playerlist.size() < match.maxPlayers())
		{
			match.state = Match.FIND_GAME;
		}
		else // Pool is full after AFK check
		{
			// This case will only happen if the bot DC's during an AFK check :P
			LeagueBot.sendMsg(LeagueBot.channel, "AFK Check! " + listNicks(match.playerlist));
			LeagueBot.sendMsg(LeagueBot.channel, "The next " + match.maxPlayers() + " players to join or pass the AFK check will be matched");
			match.startAFKCheck();
		}


		// Find the best match
		int[] split = new int[g.team_size - 1]; // Clever way to match all possibilities
		int last = g.team_size * 2 - 1;

		for (int i = 0; i < split.length; i++)
			split[i] = split.length - i - 1;

		// The average rating of two teams and a "perfect" match
		int targetR = 0;
		for (String nick : playerlist)
			targetR += getPlayer(nick).rating;
		targetR /= 2;

		// Storage for the best match
		int best = 10000;
		int[] bestp = null;

		if (g.team_size > 1)
		{
			// We're matching the last player with teammates
			int base = getPlayer(playerlist.get(last)).rating;
			while (split[0] != last)
			{
				int totalR = base;

				for (int i : split)
					totalR += getPlayer(playerlist.get(i)).rating;

				// If we found a better match, store it
				if (Math.abs(targetR - totalR) < Math.abs(best))
				{
					best = targetR - totalR;
					bestp = (int[])split.clone();
				}

				// Can't do better than 1 or 0 rating difference
				if (Math.abs(best) <= 1)
					break;

				// Clever combination rotator
				int i;
				for (i = 0; i < split.length; i++)
				{
					split[i]++;
					if (split[i] != last - i)
						break;
				}

				if (i != split.length)
				{
					for (i -= 1; i >= 0; i--)
					{
						split[i] = split[i+1] + 1;
					}
				}
			}

			// Order the teams as first N are blue, last N are purple
			// If the found team had less than or equal rating, they're team 1
			if (best >= 0)
			{
				// Remove from first to last, then insert at beginning
				for (int i = bestp.length - 1; i >= 0; i--)
				{
					String temp = playerlist.get(bestp[i]);
					playerlist.remove(bestp[i]);
					playerlist.add(0, temp);
				}

				// Have to do the last player separately
				String temp = playerlist.get(last);
				playerlist.remove(last);
				playerlist.add(0, temp);
			}
			else
			{
				// Remove from last to first, then add to end
				for (int i = 0; i < bestp.length; i++)
				{
					String temp = playerlist.get(bestp[i]);
					playerlist.remove(bestp[i]);
					playerlist.add(temp);
				}
			}
		}
		else
		{
			// 1v1 specific
			String temp = playerlist.get(0);
			if (getPlayer(temp).rating > getPlayer(playerlist.get(1)).rating)
			{
				playerlist.remove(0);
				playerlist.add(temp);
			}

			best = getPlayer(playerlist.get(1)).rating - (getPlayer(playerlist.get(0)).rating + getPlayer(playerlist.get(1)).rating) / 2;
		}

		// Generate the game info
		char l1 = (char)(LeagueBot.rand.nextInt(21) + 1);
		char l2 = (char)LeagueBot.rand.nextInt(5);
		char l3 = (char)(LeagueBot.rand.nextInt(21) + 1);
		g.id = matchHistory.size();

		if (l1 >= 'e' - 'a') l1++;
		if (l1 >= 'i' - 'a') l1++;
		if (l1 >= 'o' - 'a') l1++;
		if (l1 >= 'u' - 'a') l1++;

		if (l2 == 4) l2 = 20; // u
		if (l2 == 3) l2 = 14; // o
		if (l2 == 2) l2 = 8;  // i
		if (l2 == 1) l2 = 4;  // e

		if (l3 >= 'e' - 'a') l3++;
		if (l3 >= 'i' - 'a') l3++;
		if (l3 >= 'o' - 'a') l3++;
		if (l3 >= 'u' - 'a') l3++;

		String gamename = "RIHL" + g.id; //(g.id % 100);
		String pass = "" + (char)('a' + l1) + (char)('a' + l2) + (char)('a' + l3);

		// Put the players on their teams and message them the game info
		String blues = "";
		String purps = "";
		String host = playerlist.get(0); // Host is the first player on the blue team
		boolean isblue = false;

		// Manually add the host in
		gameMap.put(host, g);
		g.players.add(host);
		g.team1.add(host);

		// Building the team strings
		for (int i = 1; i < playerlist.size(); i++)
		{
			String player = playerlist.get(i);

			gameMap.put(player, g);
			g.players.add(player);
			if (i < g.team_size)
			{
				g.team1.add(player);
				blues += "," + player;
			}
			else
			{
				g.team2.add(player);
				purps += "," + player;
			}
		}

		// Send the host info
		LeagueBot.sendMsg(host, "Create the game named: " + gamename + " -- Password: " + pass + " -- You are on the Blue team");

		LeagueBot.sendMsg(host, "After you have created the game, send the following messages (just copy paste):");
		if (blues.length() > 0)
			LeagueBot.sendMsg(host, "/msg " + blues.substring(1) + " Join the game named: " + gamename + " -- Password: " + pass + " -- You are on the Blue team");
		if (purps.length() > 0)
			LeagueBot.sendMsg(host, "/msg " + purps.substring(1) + " Join the game named: " + gamename + " -- Password: " + pass + " -- You are on the Purple team");

		// Build the public message
		best = Math.abs(best);
		String msg = "Game " + g.id + " ready for " + match.getType(false) + ", the players are: \nBlue Team: [host] ";
		msg += listPlayers(g.team1);
		msg += String.format(" (%.1f%%)", 100 / (1 + Math.pow(10, best / 200f)));
		msg += "\n--VS--\nPurple Team: ";
		msg += listPlayers(g.team2);
		msg += String.format(" (%.1f%%)", 100 / (1 + Math.pow(10, -best / 200f)));

		games.add(g);
		matchHistory.put(g.id, "");
		dumpGames();
		dumpLobby();

		LeagueBot.sendMsg(LeagueBot.channel, msg);
		LeagueBot.sendMsg(LeagueBot.channel, "Please wait for the host to send you the game information");
	}

	/**
	 * Marks off a player's cancel vote
	 * Cancels the game if enough reports have come in
	 */
	public void handleCancel(String nick)
	{
		if (!isInGame(nick))
			return;

		nick = nick.toLowerCase();
		Game g = gameMap.get(nick);

		g.cancels.add(nick);
		if (g.cancels.size() > g.team_size)
		{
			resolveGame(g.id, "void");

			LeagueBot.sendMsg(LeagueBot.channel, "Game " + g.id + " canceled (by vote)");
		}
		else if (g.cancels.size() == (g.team_size + 1) / 2)
		{
			LeagueBot.sendMsg(LeagueBot.channel, "Game " + g.id + " has received " + g.cancels.size() + " cancel requests, " + (g.team_size + 1 - g.cancels.size()) + " more required");
		}
	}

	/**
	 * Marks off a players win report
	 * Triggers a game resolution if enough reports have come in
	 */
	public void handleReport(String nick, boolean won)
	{
		if (!isInGame(nick))
			return;

		nick = nick.toLowerCase();
		Game g = gameMap.get(nick);

		// Allow reports to change
		if (g.reports.containsKey(nick))
		{
			// Don't allow multiple reports
			if (g.reports.get(nick) == won)
				return;

			if (g.team1.contains(nick))
			{
				if (won)
				{
					g.t1w++;
					g.t1l--;
				}
				else
				{
					g.t1w--;
					g.t1l++;
				}
			}
			else if (g.team2.contains(nick))
			{
				if (won)
				{
					g.t2w++;
					g.t2l--;
				}
				else
				{
					g.t2w--;
					g.t2l++;
				}
			}
		}
		else
		{
			if (g.team1.contains(nick))
			{
				if (won)
					g.t1w++;
				else
					g.t1l++;
			}
			else if (g.team2.contains(nick))
			{
				if (won)
					g.t2w++;
				else
					g.t2l++;
			}
		}

		g.reports.put(nick, won);

		// Check if there have been enough reports
		int major = g.team_size/2 + 1;
		if ((g.t1w >= major || g.t1l >= major) && (g.t2w >= major || g.t2l >= major))
		{
			// Check for contested results
			if ((g.t1w >= major && g.t2w >= major) || (g.t1l >= major && g.t2l >= major))
			{
				// Only send the message once
				if (!g.contested)
				{
					LeagueBot.sendMsg(LeagueBot.channel, "Game ID " + g.id + " is contested");
					LeagueBot.sendMsg(LeagueBot.achannel, "SYSTEM - Game ID " + g.id + " is contested");
					LeagueBot.logAdmin("SYSTEM - Game ID " + g.id + " is contested");
				}
				g.contested = true;
				return;
			}
			else
			{
				resolveGame(g);
				return;
			}
		}

		// Print a message if there have been enough reports
		if (g.team1.contains(nick))
		{
			if (g.t1w == major)
				LeagueBot.sendMsg(LeagueBot.channel, "Blue team reported a win for Blue in Game " + g.id);
			else if (g.t1l == major)
				LeagueBot.sendMsg(LeagueBot.channel, "Blue team reported a win for Purple in Game " + g.id);
		}
		else if (g.team2.contains(nick))
		{
			if (g.t2w == major)
				LeagueBot.sendMsg(LeagueBot.channel, "Purple team reported a win for Purple in Game " + g.id);
			else if (g.t2l == major)
				LeagueBot.sendMsg(LeagueBot.channel, "Purple team reported a win for Blue in Game " + g.id);
		}
	}

	/**
	 * Marks off a player's noshow report
	 */
	public void handleNoShow(String reporter, String summ)
	{
		String target = getNick(summ);
		if (target == null)
			target = summ;

		target = target.toLowerCase();
		if (!isInGame(reporter) || !isInGame(target))
			return;

		Game g = gameMap.get(reporter.toLowerCase());
		reporter = reporter.toLowerCase();

		if (g.players.contains(target) && (!g.noshow_reports.containsKey(reporter) || !g.noshow_reports.get(reporter).contains(target)))
		{
			if (!g.noshow_marks.containsKey(target))
				g.noshow_marks.put(target, 0);
			g.noshow_marks.put(target, g.noshow_marks.get(target) + 1);

			if (!g.noshow_reports.containsKey(reporter))
				g.noshow_reports.put(reporter, new HashSet<String>());
			g.noshow_reports.get(reporter).add(target);

			// Handle sufficient noshow calls
			int req_votes = g.team_size + 1;
			if (g.noshow_marks.get(target) == req_votes)
			{
				g.noshow = target; // Flag for resolve game
				resolveGame(g);
			}
			else if (g.noshow_marks.get(target) == req_votes / 2)
			{
				LeagueBot.sendMsg(LeagueBot.channel, pSumm.get(summ.toLowerCase()) + " has received " + (req_votes / 2) + " noshow reports, " + (req_votes - req_votes / 2) + " more required");
			}
		}
	}

	/**
	 * Marks off a player's leaver report
	 */
	public void handleLeaver(String reporter, String summ)
	{
		String target = getNick(summ);
		if (target == null)
			target = summ;

		target = target.toLowerCase();
		if (!isInGame(reporter) || !isInGame(target))
			return;

		Game g = gameMap.get(reporter.toLowerCase());
		reporter = reporter.toLowerCase();

		if ((g.team1.contains(target) || g.team2.contains(target)) && (!g.leaver_reports.containsKey(reporter) || !g.leaver_reports.get(reporter).contains(target)))
		{
			if (!g.leaver_marks.containsKey(target))
				g.leaver_marks.put(target, 0);
			g.leaver_marks.put(target, g.leaver_marks.get(target) + 1);

			if (!g.leaver_reports.containsKey(reporter))
				g.leaver_reports.put(reporter, new HashSet<String>());
			g.leaver_reports.get(reporter).add(target);

			// Handle sufficient leaver calls
			int req_votes = g.team_size + 1;
			if (g.leaver_marks.get(target) == req_votes)
			{
				LeagueBot.sendMsg(LeagueBot.channel, pSumm.get(summ) + " has been reported as a leaver");
				g.leavers.add(target); // Flag for resolve game
			}
			else if (g.leaver_marks.get(target) == req_votes / 2)
			{
				LeagueBot.sendMsg(LeagueBot.channel, pSumm.get(summ) + " has received " + (req_votes / 2) + " leaver reports, " + (req_votes - req_votes / 2) + " more required");
			}
		}
	}

	/**
	 * Sends the given nick a list of contested games
	 */
	public void sendContested(String nick)
	{
		StringBuffer buff = new StringBuffer();

		for (Game g : games)
		{
			if (g.contested)
			{
				buff.append(g.id + ": ");
				for (String player : g.team1)
				{
					buff.append(getSumm(player) + " ");
					if (g.reports.containsKey(player))
					{
						if (g.reports.get(player))
							buff.append("(win) ");
						else
							buff.append("(lose) ");
					}
					else
						buff.append("(none) ");
				}

				buff.append("--VS-- ");

				for (String player : g.team2)
				{
					buff.append(getSumm(player) + " ");
					if (g.reports.containsKey(player))
					{
						if (g.reports.get(player))
							buff.append("(win) ");
						else
							buff.append("(lose) ");
					}
					else
						buff.append("(none) ");
				}

				buff.append("\n");

			}
		}

		if (buff.length() == 0)
			LeagueBot.sendMsg(nick, "No games are contested at this time");
		else
			LeagueBot.sendMsg(nick, buff.toString());
	}

	/**
	 * Re-resolves a game, undoing stat changes if necessary
	 * Triggered via !setwin
	 * Returns a String describing an error in resolution if any
	 */
	public String resolveGame(int id, String team)
	{
		// Check if the game was already finished
		if (matchHistory.containsKey(id) && matchHistory.get(id).length() > 0)
		{
			String hist = matchHistory.get(id);
			String nhist = hist;

			// Can't do anything about noshows
			if (hist.contains("NoShow:"))
				return "NoShow matches cannot be resolved";

			// Figure out how to change the result
			boolean bluewin = false;
			boolean voided = false;
			boolean wasvoided = false;
			if (team.equals("blue"))
				bluewin = true;
			else if (team.equals("purple"))
				bluewin = false;
			else if (team.equals("void"))
				voided = true;
			else
				return "Invalid resolution";

			// No change necessary
			if (!voided)
			{
				if ((bluewin && hist.contains("Blue wins")) || (!bluewin && hist.contains("Purple wins")))
					return "No change necessary";
			}
			else
			{
				if (hist.contains("Voided"))
					return "No change necessary";
			}

			if (voided)
				bluewin = hist.contains("Blue wins");
			else
				wasvoided = hist.contains("Voided");

			// Reconstruct the game from the log
			int idx = nhist.lastIndexOf(";");
			nhist = nhist.substring(idx + 1).trim();

			// Have hist contain leaver info
			hist = hist.substring(0, idx);
			idx = hist.lastIndexOf(";");
			if (idx != -1)
				hist = hist.substring(0, idx + 2);
			else
				hist = hist.substring(0, hist.indexOf(" ") + 1);

			// Reconstruct the teams
			String[] teams = nhist.split("--VS--");
			String[] team1 = teams[0].trim().split("\\)");
			String[] team2 = teams[1].trim().split("\\)");

			ArrayList<Player> team1p = new ArrayList<Player>();
			for (int i = 0; i < team1.length; i++)
			{
				team1[i] = team1[i].trim();

				idx = team1[i].indexOf("(");
				Player p = new Player();
				p.summoner = team1[i].substring(0, idx).trim();
				p.rating = Integer.parseInt(team1[i].substring(idx + 1, team1[i].indexOf(" ", idx)));

				// Temporarily use p.games to store old rating change
				String temp = team1[i].substring(team1[i].indexOf(" ", idx) + 1);
				p.games = Integer.parseInt(temp.substring(1));
				if (temp.startsWith("-"))
					p.games = -p.games;

				team1p.add(p);
			}

			ArrayList<Player> team2p = new ArrayList<Player>();
			for (int i = 0; i < team2.length; i++)
			{
				team2[i] = team2[i].trim();

				idx = team2[i].indexOf("(");
				Player p = new Player();
				p.summoner = team2[i].substring(0, idx).trim();
				p.rating = Integer.parseInt(team2[i].substring(idx + 1, team2[i].indexOf(" ", idx)));

				// Temporarily use p.games to store old rating change
				String temp = team2[i].substring(team2[i].indexOf(" ", idx) + 1);
				p.games = Integer.parseInt(temp.substring(1));
				if (temp.startsWith("-"))
					p.games = -p.games;

				team2p.add(p);
			}

			int[] ratings = new int[team1p.size()*2];
			int[] change = new int[team1p.size()*2];

			// Recalculate ratings, note that bonus k from early games is removed
			// Get average ratings for both teams
			double avg1 = 0;
			for (Player p : team1p)
				avg1 += p.rating;
			avg1 /= team1p.size();

			double avg2 = 0;
			for (Player p : team2p)
				avg2 += p.rating;
			avg2 /= team2p.size();

			// Process team1 first
			int i = 0;
			double sa = (bluewin ? 1 : 0);
			double ea = 1 / (1 + Math.pow(10, (avg2 - avg1) / 80));
			for (Player p : team1p)
			{
				Player p2 = getPlayer(getNick(p.summoner));
				ratings[i] = p.rating;

				if (!voided)
				{
					double k = 26;

					// Edit K if low rating and won or high rating and lost
					ratings[i] = p.rating;
					if ((sa == 0 && p.rating > avg1) || (sa == 1 && p.rating < avg1))
						k += Math.abs(avg1 - p.rating) / 10; // magic number alert

					if (team1p.size() == 3) // Less rating for TT
						k *= .7;

					change[i] = (int)(k * (sa - ea) + (sa == 1 ? 0.5 : -0.5)); // Rounded
					p2.rating += change[i] - p.games; // Remove old rating change, add on new

					// Edit wins, only remove win if wasn't voided before
					if (sa == 1)
						p2.wins++; // Add on new win
					else if (!wasvoided)
						p2.wins--; // Remove old win

					// Add a game if it was voided
					if (wasvoided)
						p2.games++;
				}
				else
				{
					// Remove old rating
					p2.rating -= p.games;

					// Remove voided win
					if (sa == 1)
						p2.wins--;

					// Remove game
					p2.games--;
				}

				i++;
			}

			// Process team2
			sa = 1 - sa;
			ea = 1 - ea;
			for (Player p : team2p)
			{
				Player p2 = getPlayer(getNick(p.summoner));
				ratings[i] = p.rating;

				if (!voided)
				{
					double k = 26;

					// Edit K if low rating and won or high rating and lost
					if ((sa == 0 && p.rating > avg2) || (sa == 1 && p.rating < avg2))
						k += Math.abs(avg2 - p.rating) / 10; // magic number alert

					if (team1p.size() == 3) // Less rating for TT
						k *= .7;

					change[i] = (int)(k * (sa - ea) + (sa == 1 ? 0.5 : -0.5)); // Rounded
					p2.rating += change[i] - p.games; // Remove old rating change, add on new

					// Edit wins, only remove win if wasn't voided before
					if (sa == 1)
						p2.wins++; // Add on new win
					else if (!wasvoided)
						p2.wins--; // Remove old win

					// Add a game if it was voided
					if (wasvoided)
						p2.games++;
				}
				else
				{
					// Remove old rating
					p2.rating -= p.games;

					// Remove voided win
					if (sa == 1)
						p2.wins--;

					// Remove game
					p2.games--;
				}

				i++;
			}

			// Rebuild the result string
			StringBuffer buff = new StringBuffer(hist);
			if (voided)
				buff.append("Voided; ");
			else if (bluewin)
				buff.append("Blue wins; ");
			else
				buff.append("Purple wins; ");

			i = 0;
			for (Player p : team1p)
			{
				buff.append(p.summoner);
				buff.append(" (");
				buff.append(ratings[i]);
				buff.append(" " + (change[i] >= 0 ? "+" : ""));
				buff.append(change[i]);
				buff.append(") ");
				i++;
			}
			buff.append("--VS-- ");
			for (Player p : team2p)
			{
				buff.append(p.summoner);
				buff.append(" (");
				buff.append(ratings[i]);
				buff.append(" " + (change[i] >= 0 ? "+" : ""));
				buff.append(change[i]);
				buff.append(") ");
				i++;
			}

			// Replace the match, dump all data
			matchHistory.put(id, buff.toString());
			dumpPlayers();
			dumpHistory();

			return "valid";
		}
		// Otherwise, resolve a game in progress
		else
		{
			for (Game g : games)
			{
				if (g.id != id)
					continue;

				if (!team.equals("void"))
				{
					resolveGame(g, team.equals("blue"));
				}
				else
				{
					StringBuffer buff = new StringBuffer();
					buff.append(g.start_time + " Voided; ");
					for (String player : g.team1)
					{
						buff.append(getSumm(player));
						buff.append(" (");
						buff.append(getPlayer(player).rating);
						buff.append(" +0) ");
					}
					buff.append("--VS-- ");
					for (String player : g.team2)
					{
						buff.append(getSumm(player));
						buff.append(" (");
						buff.append(getPlayer(player).rating);
						buff.append(" +0) ");
					}

					try
					{
						BufferedWriter log2 = new BufferedWriter(new FileWriter("match_history.txt", true));
						log2.write(g.id + " ");
						log2.write(buff.toString());
						log2.write("\n");
						log2.close();
					}
					catch (Exception e) { e.printStackTrace(); }

					matchHistory.put(g.id, buff.toString());

					// Cleanup state
					games.remove(g);
					for (String player : g.team1)
						gameMap.remove(player);

					for (String player : g.team2)
						gameMap.remove(player);

					dumpGames();
				}

				break;
			}

			return "valid display";
		}
	}

	/**
	 * Resolves a game and records the result
	 * Triggered by enough reports
	 */
	private void resolveGame(Game g)
	{
		resolveGame(g, (g.t1w >= g.team_size/2 + 1));
	}

	/**
	 * Resolves the given game
	 */
	private void resolveGame(Game g, boolean bluewin)
	{
		// Temp storage for logging
		int i = 0;
		int[] ratings = new int[g.team_size*2];
		int[] change = new int[g.team_size*2];

		// No match resolution if there's a noshow
		if (g.noshow != null)
		{
			for (int j = 0; i < ratings.length; i++)
			{
				ratings[i] = 0;
				change[i] = 0;
			}

			Player p = getPlayer(g.noshow);
			p.warn += 15;
			LeagueBot.sendMsg(g.noshow, "You did not show up to a game and incurred a 15 warn penalty.  You are now at " + p.warn + " warn level");
			LeagueBot.sendMsg(LeagueBot.channel, "Match canceled due to a no show (" + getSumm(g.noshow) + ")");
			LeagueBot.sendMsg(LeagueBot.achannel, "SYSTEM - " + getSumm(g.noshow) + " did not show up to a game and incurred a 15 warn penalty");
			LeagueBot.logAdmin("SYSTEM - " + getSumm(g.noshow) + " did not show up to a game and incurred a 15 warn penalty");
		}
		else
		{
			// Adjust ratings
			//R'A = RA + K (SA - EA)
			// SA = actual
			// EA = predicted = 1 / (1 + 10^((RB-RA)/400))

			// Get average ratings for both teams
			double avg1 = 0;
			for (String player : g.team1)
				avg1 += getPlayer(player).rating;
			avg1 /= g.team_size;

			double avg2 = 0;
			for (String player : g.team2)
				avg2 += getPlayer(player).rating;
			avg2 /= g.team_size;

			// Process team1 first
			double sa = (bluewin ? 1 : 0);
			double ea = 1 / (1 + Math.pow(10, (avg2 - avg1) / 80));
			for (String player : g.team1)
			{
				Player p = getPlayer(player);

				double k = Math.max(26, 55 - p.games / 2); // magic number alert

				// Edit K if low rating and won or high rating and lost
				ratings[i] = p.rating;
				if ((sa == 0 && p.rating > avg1) || (sa == 1 && p.rating < avg1))
					k += Math.abs(avg1 - p.rating) / 10; // magic number alert

				if (g.team_size == 3) // Less rating for TT
					k *= .7;

				change[i] = (int)(k * (sa - ea) + (sa == 1 ? 0.5 : -0.5)); // Rounded
				p.rating += change[i];
				p.games++;
				p.warn--;
				if (sa == 1)
					p.wins++;

				i++;
			}

			// Process team2
			sa = 1 - sa;
			ea = 1 - ea;
			for (String player : g.team2)
			{
				Player p = getPlayer(player);

				double k = Math.max(26, 55 - p.games / 2); // magic number alert

				// Edit K if low rating and won or high rating and lost
				ratings[i] = p.rating;
				if ((sa == 0 && p.rating > avg2) || (sa == 1 && p.rating < avg2))
					k += Math.abs(avg2 - p.rating) / 10; // magic number alert

				if (g.team_size == 3) // Less rating for TT
					k *= .7;

				change[i] = (int)(k * (sa - ea) + (sa == 1 ? 0.5 : -0.5)); // Rounded
				p.rating += change[i];
				p.games++;
				p.warn--;
				if (sa == 1)
					p.wins++;

				i++;
			}
		}

		// Handle leavers
		for (String nick : g.leavers)
		{
			Player p = getPlayer(nick);
			p.warn += 36;
			LeagueBot.sendMsg(nick, "You left a game and incurred a 35 warn penalty.  You are now at " + p.warn + " warn level");
			LeagueBot.sendMsg(LeagueBot.achannel, "SYSTEM - " + getSumm(nick) + " left a game and incurred a 35 warn penalty");
			LeagueBot.logAdmin("SYSTEM - " + getSumm(nick) + " left a game and incurred a 35 warn penalty");
		}

		// Handle bad reports
		for (String nick : g.team1)
		{
			if (g.reports.containsKey(nick) && g.reports.get(nick) != bluewin)
			{
				Player p = getPlayer(nick);
				p.warn += 6;
				LeagueBot.sendMsg(nick, "You reported an incorrect game result and incurred a 5 warn penalty.  You are now at " + p.warn + " warn level");
				LeagueBot.sendMsg(LeagueBot.achannel, "SYSTEM - " + getSumm(nick) + " reported an incorrect game result and incurred a 5 warn penalty");
				LeagueBot.logAdmin("SYSTEM - " + getSumm(nick) + " reported an incorrect game result and incurred a 5 warn penalty");
			}
		}

		for (String nick : g.team2)
		{
			if (g.reports.containsKey(nick) && g.reports.get(nick) == bluewin)
			{
				Player p = getPlayer(nick);
				p.warn += 6;
				LeagueBot.sendMsg(nick, "You reported an incorrect game result and incurred a 5 warn penalty.  You are now at " + p.warn + " warn level");
				LeagueBot.sendMsg(LeagueBot.achannel, "SYSTEM - " + getSumm(nick) + " reported an incorrect game result and incurred a 5 warn penalty");
				LeagueBot.logAdmin("SYSTEM - " + getSumm(nick) + " reported an incorrect game result and incurred a 5 warn penalty");
			}
		}

		// Log result
		StringBuffer buff = new StringBuffer();
		buff.append(g.start_time + " ");
		if (g.noshow != null)
		{
			buff.append("NoShow: " + getSumm(g.noshow) + "; ");
		}
		else
		{
			if (g.leavers.size() != 0)
			{
				for (String nick : g.leavers)
					buff.append("Leavers: " + getSumm(nick) + ", ");

				buff.replace(buff.length() - 2, buff.length() - 1, ";");
			}

			if (g.t1w >= g.team_size/2 + 1)
				buff.append("Blue wins; ");
			else
				buff.append("Purple wins; ");
		}

		i = 0;
		for (String player : g.team1)
		{
			buff.append(getSumm(player));
			buff.append(" (");
			buff.append(ratings[i]);
			buff.append(" " + (change[i] >= 0 ? "+" : ""));
			buff.append(change[i]);
			buff.append(") ");
			i++;
		}
		buff.append("--VS-- ");
		for (String player : g.team2)
		{
			buff.append(getSumm(player));
			buff.append(" (");
			buff.append(ratings[i]);
			buff.append(" " + (change[i] >= 0 ? "+" : ""));
			buff.append(change[i]);
			buff.append(") ");
			i++;
		}

		try
		{
			BufferedWriter log2 = new BufferedWriter(new FileWriter("match_history.txt", true));
			log2.write(g.id + " ");
			log2.write(buff.toString());
			log2.write("\n");
			log2.close();
		}
		catch (Exception e) { e.printStackTrace(); }

		matchHistory.put(g.id, buff.toString());

		// Print the result to the channel
		if (g.noshow == null)
		{
			buff = new StringBuffer();
			buff.append("Game RIHL");
			buff.append(g.id);
			buff.append(" finished: ");
			if (bluewin)
				buff.append("Blue won!\n");
			else
				buff.append("Purple won!\n");

			i = 0;
			for (String nick : g.team1)
			{
				buff.append(getSumm(nick));
				buff.append(" (" + (change[i] >= 0 ? "+" : ""));
				buff.append(change[i]);
				buff.append(") ");
				i++;
			}

			buff.append("\n");

			for (String nick : g.team2)
			{
				buff.append(getSumm(nick));
				buff.append(" (" + (change[i] >= 0 ? "+" : ""));
				buff.append(change[i]);
				buff.append(") ");
				i++;
			}

			LeagueBot.sendMsg(LeagueBot.channel, buff.toString());
		}

		// Handle warn
		for (String player : g.players)
		{
			Player p = getPlayer(player);
			p.warn = Math.max(0, p.warn);
			if (p.warn >= 150)
			{
				LeagueBot.sendMsg(player, "Your warn level has reached 150 and you have been banned as a result");

				if (p.admin)
				{
					LeagueBot.sendMsg("ChanServ", "AOP " + LeagueBot.channel + " DEL " + player);
					LeagueBot.sendMsg("ChanServ", "DEOP " + LeagueBot.channel + " " + player);
					LeagueBot.sendMsg("ChanServ", "DEHALFOP " + LeagueBot.channel + " " + player);
					LeagueBot.sendMsg("ChanServ", "DEVOICE " + LeagueBot.channel + " " + player);
				}
				else if (p.moderator)
				{
					LeagueBot.sendMsg("ChanServ", "HOP " + LeagueBot.channel + " DEL " + player);
					LeagueBot.sendMsg("ChanServ", "DEHALFOP " + LeagueBot.channel + " " + player);
					LeagueBot.sendMsg("ChanServ", "DEVOICE " + LeagueBot.channel + " " + player);
				}
				else if (p.registered)
				{
					//LeagueBot.sendMsg("ChanServ", "VOP " + LeagueBot.channel + " DEL " + player);
					LeagueBot.sendMsg("ChanServ", "DEVOICE " + LeagueBot.channel + " " + player);
				}

				p.registered = false;
				p.moderator = false;
				p.admin = false;

				LeagueBot.sendMsg(LeagueBot.achannel, "SYSTEM - Banned " + player + " for reaching 150 warn");
				LeagueBot.logAdmin("SYSTEM - Banned " + player + " for reaching 150 warn");
			}
		}

		// Update ratings
		dumpPlayers();

		// Cleanup state
		games.remove(g);
		for (String player : g.team1)
			gameMap.remove(player);

		for (String player : g.team2)
			gameMap.remove(player);

		dumpGames();
	}
}
